home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Cream of the Crop 1
/
Cream of the Crop 1.iso
/
PROGRAM
/
PNL010.ARJ
/
PNL010.TXT
< prev
next >
Wrap
Text File
|
1992-03-01
|
104KB
|
2,335 lines
Issue #10 March, 1992
========================================================================
The Pascal Newsletter (C) Copyright 1992 by Alex Boisvert
ALL RIGHTS RESERVED
[][][][][] [] [] []
[] [] [][] [] []
[] [] [] [] [] []
[][][][][] [] [] [] []
[] [] [] [] []
[] [] [] [] []
[] [] [][] []
[] [] [] [][][][][]
T H E I N T E R N A T I O N A L P A S C A L N E W S L E T T E R
========================================================================
Table of Contents
(*) Introduction ................................................. 2
by Alex Boisvert - Editor, Writer
(*) BigFiles - A Large File Viewer................................ 3
by Mich Davis - Contributing Writer
(*) BigArrays - You've Never Seen Arrays This BIG!................ 10
by Mich Davis - Contributing Writer
(*) High-Quality Sound with Turbo Pascal.......................... 13
by Alex Boisvert - Editor, Writer
(*) Overlays - How to fit your programs in memory................. 15
by Alex Boisvert - Editor, Writer
(*) Turbo Vision Without Getting GUI.............................. 19
by Richard Morris - Editor Over-the-Pond
(*) Answers to Frequently Asked Questions ........................ 26
by Trevor Carlsen - Contributing Writer
(*) Running 80286 programmes on 8088/8086 Computers............... 37
by David J. N. Begley - Contributing Writer
(*) Conclusion ................................................... 41
by Alex Boisvert - Editor, Writer
The Pascal Newsletter is published by Alex Boisvert as a medium for all
Pascal programmers to share ideas, comments, techniques or experiences
concerning any subject related to the Pascal programming language.
DataMAX BBS is the home of PNL. It can be reached in Sherbrooke,
Quebec, Canada at (819) 563-6327 or FidoNet @ 1:167/405.0 between 23:00
and 8:00 Eastern Standard Time.
Articles and source code submitted by others are the property of the
authors and are used with permission. They may not be treated
separately from this newsletter without the author's permission and thus
maintain all distribution rules of the newsletter as a whole. It may be
freely distributed in un-modified form, but no charge whatsoever may be
incurred on the recipient. All code is provided 'as-is' with no
guarantees whatsoever.
========================================================================
PNL #10 Page 2 March, 1992
=================================================================
Introduction - Editor's Note
=================================================================
After more than a year of silence, the Myth becomes a reality.
The Pascal NewsLetter has revived.
Many people will ask themselves why this is issue #10 and why wasn't PNL
published during the last year.
The facts. Pete Davis, founder and ex-editor of PNL, had personal
problems and was forced to sell his computer. He was unable to continue
his work.
I must thank him for the outstanding work he has done with PNL. He
worked very hard to keep the issues out and each of them was a success.
For the sake of the Pascal community, I am taking up the baton. I will
be publishing the Pascal NewsLetter starting with issue #10 and up. I
expect to be publishing one issue bimontly, if I have sufficient
material (articles) to fill it. Since I am starting from scratch,
everything has to be done a second time. I must find new columnists,
contributing writers and people to distribute the newsletter all around
the world. Richard Morris will be distributing it in Australia but, I
have yet to find a distributor in Europe and, possibly, in Asia.
As you may have noticed, I have changed a bit the format of the
newsletter. The table of contents now shows on the first page. This
will be handy if you are keeping the newletters on print.
I hope you all enjoy the "New PNL" and I wish that more people get
interested in it.
Alex Boisvert
PNL's New Chief Editor.
PNL #10 Page 3 March, 1992
=================================================================
BigFiles - a large file viewer
=================================================================
G'Day! Well, it sure is good to see the Pascal NewsLetter alive and well
again. In this article and the followup in the next edition, we'll be
working on a very useful tool - a technique for accessing large text
files.
On the way we'll be exploring a LOT of really neat techniques all rolled
into the one program. Unless you're a closet guru, I'm SURE you'll find
something you haven't come across yet. We'll be dabbling in:
o Take-only-what-you-need dynamic memory allocation.
o Vertical Scrolling.
o Turbo Pascal's Built-in Assembler.
o Using DOS to allocate memory.
o Treating TEXT files as TYPED files.
o Buffering Text files to improve access speed.
o A technique for quick access to any part of a text file.
o Objects.
o Very large arrays (bigger than 64k in size, or more than 64k
elements).
o A standalone program for viewing text files, a la Buerg's LIST.
o i286-capable programs.
o Horizontal scrolling.
o Multitasking.
o Turbo Vision.
o Optimization using Turbo Profiler.
o Optimization using the built-in assembler.
o DesqView-aware programs.
This is all done with Turbo Pascal 6.0. I apologise to those who don't
have 6.0, but that's progress.
Part 1:
[Note: I have a 286, so I compile my programs with $G+ on. If you don't,
remove the $G+ from compiler options, and the "Test186" from the
uses-block of each program it appears in.]
What we aim to have by the end of this article is a tool which will allow
us to view arbitrarily large text files. Each of the included programs
builds on the one before it, starting with MDP1.PAS.
The way we'll first explore is to read the lines of the file into an
array of strings. MDP1.PAS is about as simple as you can get! It has
two sections - the first section opens the file, and the second prints it
forwards and backwards. Note that this series of programs get the name
of the file to show from the command line, ie MDP1 c:\autoexec.bat. If
you forget to give it a filename, the program will try to read from the
keyboard. You can exit by pressing ^Z and Enter. Note that IDE users
can set the command-line parameters by selecting [R]un, p[a]rameters
inside the IDE, and [R]un, [A]rguments inside Turbo Debugger.
Note that this program is extremely limited:
PNL #10 Page 4 March, 1992
o It is wasteful of resources to declare an array larger than we'll
need, but for many files, 100 lines won't be enough.
o The maximum number of lines we can possibly extend this approach to,
is about 250 lines.
For these reasons, MDP1.PAS is a bit of an evolutionary dead-end.
MDP2.PAS is a little better. This program works by reading the file
line-by- line, and storing it IN ONLY AS MUCH MEMORY AS IT NEEDS to store
it. Thus, files with (on average) shorter lines will be able to have
more lines loaded before the computer runs out of memory.
This program introduces the idea of a table to point to the start of the
lines in the file. Once we have this table, it's a simple matter to look
up the line, get the address of that line in memory and print it out.
Note the table is made of records, with a 4-byte pointer field for the
start of the line, and a word for the size of the line. We need to keep
the size so that later we can feed the size to FreeMem - TP doesn't keep
track of how big the memory chunk it allocates you with GetMem is, so you
have to remind it at FreeMem time.
MDP2.PAS is limited by two things - It stores the lines from the file on
the heap, so can only handle files of about half a megabyte or less.
Secondly, the table of line pointers is limited to about 10000 entries,
since the address table can't be larger than 64k (ie, a little larger
than 6*10000).
Now let's turn our attention to something different - scrolling. Take a
look at MDP3.PAS.
Perhaps the best way to see what it does is to run it. When you do,
you'll see a line of numbers, which you can move along by using the + and
- keys.
This program has two parts. The first draws the initial screen. Note
the call to min (minimum) in the For - this roughly translates to "draw
until you hit the end of the screen, or until you run out of numbers".
The second part of the program is the standard repeat-until/readkey/case
input loop. WinTop by-the-way is the line number which is currently
displayed at the top of the screen.
Note that the - and + sections scroll the screen by using Crt's DelLine
and InsLine. An unfortunate consequence of this is that the status line
at the bottom of the screen flickers quite badly when scrolling takes
place. To stop this, you'd either have to put the status line at the top
of the screen, or declare a Crt.Window. I've done neither since I didn't
find it all that annoying. The followup article in the next PNL WILL
address this problem.
It's a good idea to step through this program using F7 to get an idea of
just how it performs the scrolling. Note that after the initial redraw,
only the lines that are just appearing are rewritten - this is faster,
and reduces the inevitable screen flash (not CGA snow) that occurs when
you update a screen line-by-line.
PNL #10 Page 5 March, 1992
Now let's have a look at MDP4.PAS. This is a hybrid - it combines the
file reading section from MDP1.PAS with the scroller from MDP3.PAS, so it
shouldn't contain too many surprises. Note that in the scrolling
section, the writeln's which outputted numbers have been replaced with a
reference to the data at that line position (eg, line [WinTop]). This is
then passed to procedure PrLn, which acts like a writeln, except that it
chops off lines that are longer than 79 characters. If you don't do
this, a long line will overflow onto the next, spoiling the display.
Until now, the programs we've looked at have been fairly elementary.
Well, you've been warned! Programs after this point get quite a bit
harder to understand, for many reasons:
o They're more sophisticated.
o They use techniques not seen in everyday Turbo Pascal programs.
o I wrote them, and you're having my less-than-perfect programming style
inflicted on you. Sorry! :-D
Let's look at three interesting areas before we proceed to MDP5.PAS. The
first is a device which dramatically speeds up TEXT file access -
SetTextBuf.
SetTextBuf is part of Turbo Pascal's Run-Time Library [RTL]. It lets you
tell Turbo Pascal to use a given area of memory as a cache or buffer when
dealing with a text file. Let's say we had the following fragment of
code:
var TBuff:array [0..8191] of byte;
f:text;
line:string;
begin
assign (f,'whatever.doc');
SetTextBuf (f,TBuff);
reset (f);
readln (f,line); readln (f,line);
close (f);
end;
Without the SetTextBuf, the two readlns would probably have cause two
disk accesses. Instead, the RTL will now try to fill TBuff with as much
data from f as it can. Then, requests to access the file come out of
TBuff, instead of an access to the disk.
If you don't already, try it in your next program which deals with lots
of text files - you'll be pleasantly surprised at the speed increase!
The second concept overcomes an annoying limitation with Turbo Pascal -
when you're reading TEXT files, you have no way of using the FilePos
function to find out where you are, or the Seek procedure to reposition
yourself. To get around this, let me introduce to you ("How do you do?")
a unit written by a fellow Australian - TextUtl2. This unit lets you
perform FilePos, FileSize and Seek on text files, and is a real life
saver!
Third, the idea of going to DOS for memory, rather than Pascal's
GetMem/FreeMem. Why? Because Turbo Pascal is limited to handing you
blocks of 64k at a time, whereas DOS will happily give you a couple of
PNL #10 Page 6 March, 1992
hundred k's of consecutive memory, which we'll be using for our mega
arrays.
Take a look at MDP5.PAS. The first thing to note is that now we don't
read in the actual data from the file - all we do is store the file
OFFSET of each line. Later when we want to go to a certain line, we'll
look up its offset, Seek to that position, and happily read away. This
lets us view up to 16000 lines, which is the limit of Vern Buerg's LIST
program.
Observe how the text buffer is set up: TBuffSize is set to how many K we
want the buffer to be (I've picked 20k). Then TBuffType is declared to
be an array of that size in K's. Note that the buffer differs from the
code fragment we saw before, because it'll come off the heap, instead of
being a static variable.
Note the call to TextFilePos on line 47. This shows just how easy it is
to use the TEXTUTL2 unit. Before each line is read, we store the offset
for that line for future reference.
If you're wide awake, you'll see that line 48 we use a readln without
telling it where to put the data it reads. This is because we're not
interested in what's in the line, just where they start. The reading of
the data will be done later.
So, the program reads in the file, and asks you for the starting line
number. When you've typed a valid number in, line 64 looks up the offset
of that line, and moves the file pointer to that position. The lines
following that read the file to cover the screen, either until the bottom
of the screen is hit, or the end of the file occurs.
Note line 71 - this is a shady trick! If you use Crt.KeyPressed, then
even when it detects a key it leaves it in the keyboard buffer. So, the
program pauses at line 71 until you press a key. When you do, it loops
back to the readln in line 61, which gets its first key free - the one
you pressed.
This trick lets you prompt for information in a way which isn't going to
spoil your nice screen layout until the user is ready to type something.
Note that you only have to perform ONE TextSeek for a whole screen. You
COULD perform a TextSeek for EVERY line on the screen, but why bother?
Reading a line leaves the file pointer (and cursor!) sitting fair and
square on the next line, all ready for you to use.
Note in line 75 we Dispose of TBuff AFTER the file has been closed.
Now we're going to sidetrack for a moment. Just for an experiment, we're
going to ask DOS for the memory for TBuff, instead of TP, which uses the
heap.
This program will be called MDP5A.PAS ("A" because it's an experiment).
But wait! Do I hear you say there's no MDP5A.PAS included? You're
right. Instead, I've put the DIFFERENCES between MDP5.PAS and MDP5A.PAS
into a file called MDP5A.EDL. This file is really a script for EDLIN
(and you thought you'd never use EDLIN again eh?!). To get MDP5A.PAS,
type this at the command line:
PNL #10 Page 7 March, 1992
C:\PNL010> edlin MDP5A.PAS < MDP5A.EDL
... and this will create MDP5A.PAS.
In fact, I've built a Turbo Vision revision control system which uses
purely DOS commands to do the nitty gritty work - it compares two files
with FC, and generates an Edlin script for the differences. I'm telling
you this because it'll be in a future edition of the Pascal Newsletter,
so keep reading! (And keep writing, else there won't BE a future
edition!)
Note that MDP5A.PAS uses a unit called DosMem. I've written this as an
easy-to-use interface to the DOS memory functions. There are three
modules:
function Alloc(paras:word): word;
procedure Free(p:word);
function Largest: word;
Note that each of these works with "paragraphs", which on PCs means
"16-bytes". Alloc is like TP's GetMem - it'll give you "paras"
paragraphs of memory, returning with the segment of the block. If the
request can't be met, it will return a segment of zero. Free is like
TP's FreeMem - it'll hand the block at "p" back to DOS when you've
finished with it. Note that you don't have to tell DOS how large the
block was - now if only FreeMem would do that! Largest is like TP's
MaxAvail - it'll tell you the largest size block you can ask for. The
result is in paragraphs.
Note that the DosMem unit is written using the Built-in assembler.
Sorry, pre-6.0 people. Needle me enough and I'll write you a .OBJ or
some inline code which does it.
Ok, back to business. Line 39 of MDP5A.PAS performs the same function as
line 39 in MDP5.PAS, except that it asks DosMem.Alloc for the memory.
Because DosMem uses paragraphs, we have to convert the buffer size in K's
(20) to paragraphs. We do this by multiplying by 64. (20k * 64 = 1280
paras = 1280 * 16 = 20480 bytes = 20k).
Note that TBuff is a pointer type, with two halves - the segment, and the
offset. The segment comes from the result of Alloc, and the offset will
always be 0. The two are joined into a standard TP pointer by the Ptr
function.
The other lines that have changed (57 and 75) pass the segment of TBuff
back to DOS - note that you do this AFTER the file is closed.
The reason we change a perfectly good program (MDP5.PAS) into one that
has to rely on DOS (!) is to give you a feel for working with DOS memory,
which is what we'll be doing in the next program.
MDP6.PAS is the first of our programs to use the BigArray unit, which is
covered in another PNL article. Basically, the BigArray unit has an
object type called BigDOSArray, which makes use of the >64k ability of
DOS's memory management to implement a VERY large array.
We'll be using one mega-array, and our data type is the longint, which
takes 4 bytes. So, line 44 tells the array object all about it. Line 45
PNL #10 Page 8 March, 1992
asks the object to say how many elements it could possibly hold, of the
type you've told it. In this case, we'll be using an element for every
line, so line 46 tells us how many lines our program is capable of
accessing.
Line 47 asks the object to set up the array as LARGE as it can possibly
be made. Note that this will consume ALL DOS memory. If you were using
more than one of these objects, you'd probably not pass it the maximum.
Line 55 is where things start to get interesting. One of BigDOSArray's
methods is called Elem. If you pass it the element number, it will
return with a pointer to where that element is stored in memory. So Elem
gets passed the line number, and LinePtr then points to where we put the
offset. Line 56 shows how the array gets its value.
Because LinePtr holds the element's address, we could just as easily read
from LinePtr^ as written to it, and that's what happens in lines 73 and
74. We find the offset of the line we want, we seek to the position, and
we read the file, just like we did in MDP5.PAS.
One last point - note the calls to the Done method of LineBank in lines
66 and 86. This hands back the memory the array consumed back to DOS.
We now have the shell of a fairly powerful program. On my machine, it
says it's happy to handle over 130,000 lines (60,000 in the TP IDE). The
fidonet nodelist (052) is 16302 lines long, and in a few weeks, Vern
Buerg's LIST program isn't going to be able to handle that. At least
you've now got something that can!
If you've understood everything so far, then relax - from now on, things
are all down hill. MDP7.PAS is just MDP6.PAS with the scrolling stuff we
worked out in MDP4.PAS. It covers nothing new, just merges the two
techniques we developed before.
MDP8.PAS is as far as we're going to take things this issue. Still, it's
quite a program! It's been split up into separate procedures (as it
should have right from the beginning!), and a number of features have
been added. First, it shows you a percentage as you load the file. This
is useful when you're loading the FidoNet nodelist, or an online version
of the Bible [one of the best reasons to download it - it gives you a
great exerciser for your text-processing programs! ;-D]
Also, it now responds to extended keys, such as the arrows, and the PgUp/
PgDn/Home/End keys. ESC has replaced 'q' for exitting, and you can press
# to jump to a certain line. This is accomplished by having TWO CASEs -
one handles extended keys, and the other, normal files.
I'm the first to admit that it's not the fastest thing around. That's
why in the next issue of PNL we'll discuss optimising it. We'll use the
profiler to investigate where the program is spending the most time
(everywhere, ie, equally slow!). We'll rewrite a large chunk of it in
assembler, and we'll use multitasking to remove the annoying delay at the
start of the program.
Also, we'll discuss how we'd port the program to TurboVision, and how to
have it run RIGHT under DesqView. Finally, we'll see what we can do
about viewing lines that are longer than the screen by horizontal
scrolling.
PNL #10 Page 9 March, 1992
Until next issue,
Make merry with your Pascal!
+-------------------------------------------+
| Mitch Davis |
| 10/1072 Whitehorse Rd., |
| Box Hill, Victoria, Australia. 3128. |
| |
| Ph. +61-3-890-2062. Fidonet: 3:634/384.6 |
+-------------------------------------------+
PNL #10 Page 10 March, 1992
=================================================================
BigArray - you've never seen arrays this BIG!
=================================================================
*** Error 22: Structure too large.
*** Error 49: Data segment too large.
*** Error 96: Too many variables.
*** Runtime error 201 at XXXX:YYYY. {Range check}
*** Runtime error 203 at XXXX:YYYY. {Heap full}
How many times have you been frustrated by these little beauties? How
many times has it been because you had an array that just wouldn't stay
down? There are two ways to fix this - you could put your thinking on a
diet and work out some other way that doesn't consume quite so much
memory (recommended!) or you could do it the lazy way, and slot in a unit
called the (drum roll!) *** BigArray ***.
BigArray will give you arrays which can be as big as will fit in
conventional memory, ie, they aren't limited to 64k. The arrays can be
of any type, and are of a single dimension. (A version being written now
lets you have arrays that spill over to disk or EMS, as well as multi
dimensionals.)
The large arrays are implemented as objects. You use the .SetElemSize
method to tell it how big your elements are, and you can subsequently
find out via .GetMaxSize how many elements the largest array can be.
Then you call the Init method with the number of elements you'd like, and
voila, your array is done! When you're finished with the array, call the
.Done method.
How are elements accessed? You pass the element subscript to the .Elem
function, and it returns a pointer to that element. The idea is that you
then assign that pointer to a pointer variable of the type you're
storing. Then by deferencing the pointer (following it by a caret [^])
you can then access any fields, bits, WHATEVER that you would with a
plain-jane variable.
Let's look at a sample of how you'd use them:
program BigArrayTest;
{Program to accompany article in issue #10 of the Pascal NewsLetter. }
{Author: Mitch Davis, (3:634/384.6) +61-3-890-2062. }
{$G+,M 16384,0,0} {Make SURE you tell TP not to steal all the DOS memory }
{for a heap! If you're not using dynamic variables, }
{set both numbers to 0. }
{This program provides a nonsense demonstration of how you use the tools }
{in the BigArray unit. }
uses Test186, BigArray; {I run my programs on a i286. If you have an XT,}
{Remove the "G+," from above and the "Test186" }
{from the uses section. }
PNL #10 Page 11 March, 1992
type PigeonType = record
value:real;
changed:boolean;
end;
var PigeonHole:BigDosArray;
PigeonPtr:^PigeonType;
PHnum, MaxSize:longint;
GlobalChanged:boolean;
begin
writeln ('Welcome to the pigeon-hole.');
with PigeonHole do begin {This sets up the big array.}
SetElemSize (sizeof (PigeonType));
{Tells it how big each element will be}
MaxSize := GetMaxSize;
Init (MaxSize); {Make it as big as possible.
This is not compulsory. }
end;
writeln ('There are ',MaxSize,' pigeon-holes, numbered from 1 to '
,MaxSize,'.');
write ('Please wait while I clear them... ');
for PHnum := 1 to MaxSize do begin
PigeonPtr := PigeonHole.Elem (PHnum); {Get the address of the element}
PigeonPtr^.Changed := false; {Reference the changed field within that}
end; {element. }
writeln ('Done.');
GlobalChanged := false;
{This will save us search time later if no changes}
repeat
write ('Which pigeon hole? (1-',MaxSize,', 0 to quit): ');
readln (PHnum);
if (PHnum > 0) and (PHnum <= MaxSize) then begin
PigeonPtr := PigeonHole.Elem (PHnum);
with PigeonPtr^ do begin
case Changed of
true :writeln ('That pigeon-hole has the value of ',Value:3:2);
false:writeln ('That is a new pigeon hole.');
end;
write ('Change it to? ');
readln (Value);
Changed := true; {means this ph will be shown in the end summary}
end;
writeln ('Pigeon-hole changed.');
GlobalChanged := true;
end;
until PHnum = 0;
writeln ('-------------------------------------------------');
case GlobalChanged of
false:writeln ('You didn''t change any pigeon holes.');
true :begin
writeln ('The pigeon holes you changed were:');
write ('Wait..',#13);
for PHnum := 1 to MaxSize do begin {scan thru the ph's}
PigeonPtr := PigeonHole.Elem (PHnum);
with PigeonPtr^ do
if changed then writeln (PHnum,': ',value:3:5);
PNL #10 Page 12 March, 1992
end;
end;
end;
writeln ('Thanks for using the pigeon-holes!');
PigeonHole.Done;
end.
If you understand this (in conjunction with the "interface" section of
the BigArray unit), then you should have no problem working out how to
use it.
A few usage notes:
o The Big Arrays are 1-based, that is if you call
BigDOSArray.Init(10), then the element subscripts run from 1 up to
10.
o There is NO checking to ensure that the element you pass to
BigDOSArray.Elem is within the defined range. If it isn't, then
you'll get garbage results for the pointer result. Beware!
o The code for BigDOSArray hasn't been extensively tested. Caveat
Emptor!
o BigDOSArray needs optimising BADLY! I plan to write it in
optimised assembler throughout; in the meantime, you'll have to
sacrifice speed for convenience.
o I am aware that Turbo Power markets code which implements large
arrays (and probably MUCH better than I've managed here!). I
haven't looked at their code. Trevor Carlsen's OverSize unit also
performs a similar function. I looked at his code, and couldn't
understand it. That's not meant as a slur on Trevor (3:690/644),
who writes code so good it should be framed. I just thought I'd
take a different approach. Upshot: If any of the other large-array
products fit your bill better, use them!
o The BigDOSArray unit currently compiles in i286 mode. If you have
an XT or similar, chop out the G+ and Test186 bits, in the same way
as the comments in the demo program mention.
Well that about wraps it up. I'd be more than happy to answer any
queries you have about the unit. Next issue, time and space permitting,
I'm planning on having a followup article which goes into optimisation,
multi-dimensional arrays, etc. Till then? Make merry with your Pascal!
+-------------------------------------------+
| Mitch Davis |
| 10/1072 Whitehorse Rd., |
| Box Hill, Victoria, Australia. 3128. |
| |
| Ph. +61-3-890-2062. Fidonet: 3:634/384.6 |
+-------------------------------------------+
PNL #10 Page 13 March, 1992
=================================================================
High Quality Sound in Turbo Pascal... Not Really.
=================================================================
Many Turbo Pascal programmers find that the SOUND procedure does not
produce anything interesting as far as music goes. This is not due to
Turbo Pascal's limitation to use the speaker but due to the speaker
itself. The original PC speaker is almost useless... Unless you happen
to have the public-domain utility called RESPLAY, written by Mark J. Cox
and (of course) Turbo Pascal.
--- From RESPLAY v1.0 documentation: ---
"RESPLAY is a memory resident program designed to help high-level
language programmers make the most of the PCs useless speaker. By use of
some simple procedures, you can playback digital samples from within your
programs and sample yourself. Playback can either be to the PC speaker
(no too bad) or to some external hardware [...] "
Possible uses of RESPLAY:
o Arcade Games ("He's Dead Jim!" or realistic shooting noises)
o Sound Analysis (Fast Fourier Transforms/ analysing spoken words)
o Spelling Games for kids.
o Sampling your friends and making them sound silly (by changing
their speed, or by FFTs and altering their pitch!)
o The next 'Jive Bunny' single
------------------------------------------
Resplay is fairly easy to use from Turbo Pascal because it uses interrupt
calls. Simply fill the register set and call interrupt 2F. If you have
any knowledge of assembler, RESPLAY will work for you!
The original archive file of RESPLAY contains a C source code that I have
"ported" to Turbo Pascal. I say "ported" because the two programs work
differently with memory (loading & storing digital samples) but they both
do the same thing: Play a digital sample file.
I have done much of the work for you. I have created a Pascal unit which
can read and play any sample. I have used TP's INTR procedure to call
the interrupt instead of direct BASM instructions for compatibility
purposes. Turbo Pascal v5.5 users will be able to use this code without
changing one line of code.
The unit is name DIGISND.PAS and the program is PLAYDIGI.PAS.
I must admit that the program is not fully optimized. It reads the
sample file by allocating 64k memory segments and plays these segments
one after the other... I have played samples up to 350k with it. If the
sample is bigger than 64k, you will notice some delay (under 10 ms.)
between each 64k "chunck". It is possible to tell RESPLAY to play the
whole sample if the sample is stored continuously in memory. I have
decided to play the game safely and play the samples by 64k segments.
The code is object-oriented so that anyone wishing to modify it may do so
by creating a descendant object type. Someone might want to add a method
to play only part of the sample read (eg. from time x to time y), play
PNL #10 Page 14 March, 1992
the sample on a COVOX card instead of the PC speaker, etc...
Please refer to RESPLAY1.ZIP before modifying the unit.
[Editor's Note: RESPLAY1.ZIP has been included in the distribution
archive of PNL#10. It contains the original documentation and a digital
sample.]
By the way, I have tested RESPLAY with my program on a COVOX card and the
resulting "music" is better than with a SoundBlaster card. Also, try
getting Macintosh (Yes, Macintosh!) digital samples, they work just fine
with RESPLAY.
Hope you have fun with your PC speaker now!
Alex Boisvert
DataMAX Communications Enr.
FidoNet @ 1:167/405
PNL #10 Page 15 March, 1992
=================================================================
Overlays - How to fit your programs in memory
=================================================================
Overlays in Turbo Pascal help you, the programmer, to write programs that
otherwise would not fit into the 640k limitation of DOS. They can also,
in special cases, reduce your code's memory requierement in order to get
more memory for dynamic allocations.
To accomplish this, overlays are "parts" of your programs that are stored
on disk and which are controlled by the program, meaning that they are
loaded in memory only when they are needed. The principle is to allocate
the same (limited) memory space to several sections of program... at
different time obviously.
The major drawback of using overlays is the time involved in loading the
useful sections at run time, or "on-the-fly". You will therefore use
this method only when available memory is less than your program's size.
Your programs will then become independent of RAM memory available, to
which the computer has access to.
The developpers' of Turbo Pascal have kept to a minimum the rules to obey
when using the overlays. Management of overlays is handled by the
routines of the OVERLAY unit, which you will have to add to your USES
statement at the beginning of your main program.
The smallest portion of code which can be declared as "overlayed" is a
unit. With Turbo Pascal, it is not possible to overlay specific
routines. Each unit is loaded in the heap memory zone, which is the
reserved memory between the stack and the memory of dynamically allocated
variables.
When using overlays, you must declare all your program's routines as FAR.
The easiest way to do this is to add the following compiler directive
{$F+},
just after your program's header. Any unit compied with the directive
{$O+},
can then be compiled as an overlayed unit, but they do not have to. This
directive will tell the compiler to produce the needed overlay control
code.
The following compiler directive
{$O UNIT_NAME}
will indicate if a unit specified in the USES statement will, in fact, be
overlayed. It is therefore possible to use this unit without
"overlaying" it simply by using the USES statement and ommiting the {$O
UNIT_NAME} directive. This unit would then be treated "normally", as any
other unit declared by the USES statement.
This represents an alternative. You can overlay the unit or not...
depending on the program using it.
PNL #10 Page 16 March, 1992
When you use the overlay technique, you have to specify the OVERLAY unit
in the USES statement and, initialize overlay management by calling the
OVRINIT('filename.OVR'),
routine. It is best if this is done right at the beginning of the
program. Here is an example:
PROGRAM myprogram;
{$F+} {forces FAR calls}
USES
OVERLAY, CRT, {TP 6 units}
myunit1, myunit2, myunit3; {your own units}
{$O myunit1}
{$O myunit2} {Overlay these two units ... }
{Note that "myunit3" is overlayed.}
{...}
{declaration goes here}
{...}
BEGIN {main program}
OVRINIT('MYPROG.OVR');
{...}
{other commands here}
{...}
END.
In this case, the compiler creates two files: MYPROG.EXE, the executable
file, and MYPROG.OVR, the overlay file.
Now, the following example will demonstrate the basics of overlay
management. The program will integrate two units, OVER1 and OVER2, and
call two routines that they contain: HELLO1 and HELLO2. These two
procedures will simply display a message.
Here are the two overlays:
Unit OVER1; {OVER1.PAS}
{$F+,O+}
Interface
procedure HELLO1;
Implementation
uses
crt; {TP unit}
procedure HELLO1;
begin
writeln('Hello from unit OVER1.');
delay(1000);
end;
end. {end of unit OVER1}
PNL #10 Page 17 March, 1992
Unit OVER2; {OVER2.PAS}
{$F+,O+}
Interface
procedure HELLO2;
Implementation
uses
crt; {TP unit}
procedure HELLO2;
begin
writeln('Hello from unit OVER2.');
delay(1000);
end;
end. {end of unit OVER2}
Now, compile the sources of the two overlays. Once this is done, your
disk will contain two source files OVER1.PAS and OVER2.PAS and also their
corresponding compiled TPU unit OVER1.TPU and OVER2.TPU. Do not forget
to select the option "Compile/Destination" to Disk, otherwise, the two
TPU files will not be created.
The main program looks like this:
program OverlayDemo; {OVRDEMO.PAS}
{$F+}
uses
overlay, crt, {TP units}
over1, over2; {overlays}
{$O over1}
{$O over2}
var
i: integer; {loop counter}
begin {main program}
ovrinit('ovrdemo.ovr'); {initialization}
for i:= 1 to 10 do
begin
clrscr;
Hello1; {call routine in unit OVER1}
Hello2; {call routine in unit OVER2}
end;
write('Program is finished. Please press [ENTER].');
readln;
end. {program}
The two units merged together to form the overlay OVRDEMO.OVR when
compiling the program. Compiling will also (obviously) create the
executable OVRDEMO.EXE.
The overlay file must always reside in the same directory as the
executable file but it is also possible to merge the overlay file to the
.EXE file with the command
PNL #10 Page 18 March, 1992
COPY /B ovrdemo.EXE + ovrdemo.ovr
which, in fact, appends the overlay at the end of the .EXE file. In this
case, the command OvrInit('ovrdemo.EXE') will initialize the overlays
integrated in the .EXE file.
As you have seen by now, the overlays are not difficult to implement.
Just a few modifications around the USES statements and the addition of
the compiler directives {$O ...} and {$F+} can lead to a program which is
less RAM memory dependent. You may wish to consult your Turbo Pascal
manuals for further informations on using EMS memory with overlays,
clearing the overlay buffer (increasing dynamically-"allocatable" memory)
and modifying its size.
I have used overlays largely with the Object Professional Library from
Turbo Power because object-oriented programs tend to create larger
programs because the compiler cannot easily "strip" unused virtual
objects out of the .EXE file.
For obviously reasons, another drawback of using overlays is that your
program cannot be compressed by a program like PKLITE or LZEXE, which
dynamically uncompress your program in memory when executed. The overlay
file has to stay in its original uncompressed form on disk because this
is how your .EXE file expects to read it.
Alex Boisvert
DataMAX Communications Enr.
FidoNet @ 167/405
PNL #10 Page 19 March, 1992
=================================================================
Turbo Vision without getting GUI.
Copyright (c)1991 Richard A. Morris
=================================================================
INTRODUCTION
Around November of 1990, as most of us know, Borland released Turbo
Pascal 6.0, and with their add-in toolbox Turbo Vision unveiled a new
Programming Methodology for Turbo Pascal called Event Driven Object
Oriented Programming.
Well, did the Fewmets hit the Windmill or What !?!
Erstwhile conservative commercial programmers ranted that TV was every
thing from a Goblin to a toy toolbox destined to be unsupported by
Borland, they complained that it consisted merely of cute saccharine
windows, of little or no practical use in professional projects. The
silence from a lot of Third-Party Code Shops, who had always provided
outstanding and timely Developer support packages, was deafening as they
waited to see how the programming community would react (Blaise being the
only exception). In a classic Catch-22, Programmers have been warily
watching Borland to see if they will support Turbo Vision, Meanwhile
Borland, and third-party Code shops, quietly had their eyes on the Turbo
Pascal community to see if TV was to become popular enough to support,
potentially making the bitching of disgruntled users a self-fulfilling
prophesy.
It's now a year on, slowly but surely Programming journals, and Reference
Books are starting to constructively cover the complexities of TV (Most
notably Messrs. Rubenking and Duntemann, thank you sirs). OK so why do
so many good Pascal programmers have so much dirt to throw at TV, surely
they must know what they're talking about? Well TV is a not merely
another toolbox full of useful functions, but rather an application
framework that impertinently confronts the programmer to completely
rethink his/her programming design, from scratch. It's a big ask for a
lot of us who have several years of training invested in our trade. Most
of us looked at the Demos, and thought 'Well I'm certainly not going to
put my name on such a "Mickey-Mouse" program. Wonder if I can make it
look a bit more professional?', had a look at the code and found it too
difficult to personalize the interface, and so gave up any attempt at
using it, and concentrated on our own custom interfaces that we'd
invested so much skull sweat upon.
Two programmers I have much respect for, convinced me by their comments
to take a second look at TV. Is it worth worrying about then, well in a
my native vernacular, 'Bloody Oath'! I must add that it took me several
months of painful re-education to bend my Object Oriented programing
abilities around TV. Turbo Vision is certainly no beginners toolbox as
it would appear from Borland press releases and manuals, this may have
something to do with the sluggish move of programmers to using it.
Firstly any prospective programmer must firmly understand the usage of
Pointers and heap memory, Next Object Oriented principles must be almost
second nature, Finally the Event Driven paradigm should be well
understood. Them's three steep learning curves! The next problem as
outlined by Jeff Duntemann in Dr Dobbs Journal Nov, 1991 in his
PNL #10 Page 20 March, 1992
"Structured Programming" column (Read it!), is that there no "Front Door"
to start off your Research, that you must digest Turbo Vision in a whole
gestalt.
I plan to do some articles on my experience of coming to grips with Turbo
Vision in the next few issues of PNL, however today I'll start with a few
nice little goodies that come with TV, which can be used independently of
the Application Framework. This allows us to at least deal with some of
the TV topics without haveing to see the "Big Picture" at once. Let's
discuss TV's Objects Unit, which contains among other goodies
Collections, and Stream. You should have a fair grasp of Object oriented
programming, and naturally a good concept of dynamic memory and heap
usage.
INTERESTING OBJECTS
Lurking in the set of Turbo Vision Units that comes free with every copy
of Turbo Pascal 6.0 is the Unit "Objects.TPU". It's well worth looking
into this Unit to see the usefull routines and objects that the Turbo
Vision User interface uses as it's glue to bind itself together. I'll
leave Collections and Streams for last.
Open the file Objects.Int (In your DOC subdirectory of a standard turbo
pascal setup), and open your Turbo Vision Guide to page 190. Refer often
to the Global reference from page 327 as we talk of Types, procedures,
and functions, and the Object Reference from page 205 as we talk of
Objects.
TYPE CONVERSION TYPES
One of the main advantages and disadvantages of Pascal has always been
the strong typing. This means that if you have a variable of a certain
type you can't usually assign it's contents to a variable of another
type. The advantage is that it saves the unwary programmer writing a
large variable over a smaller variable and corrupting the memory of all
variables following. The disadvantage is that it constrains the
programmer from passing data between dissimilar types.
Turbo pascal changed all this by allowing you to typecast between any two
types of the same size, for example a Pointer and a longint are both 4
bytes long so we can do the following;
Var
Ptr : Pointer;
Long: Longint;
begin
Long := Longint(Ptr);
end;
And the Long variable will contain a copy of the bytes of information in
the Pointer variable, this may not sound particularly usefull. But
consider the WordRec, it allows you to extract the high and low Bytes
from a word, Visualise this example for Colours.
Var
MyColour : Word;
PNL #10 Page 21 March, 1992
MyBackground : Byte;
Const
Black = 0; White = 7;
begin
WordRec(MyColour).Hi := Black;
WordRec(MyColour).Lo := White;
MyBackground := WordRec(MyColour).Hi;
end;
Then we have the Pointer array types which can be type cast over a
pointer to access the pointers contents as an array of Bytes, or Words.
Remember you can typecast types of the same size, and all pointers are 4
bytes in size regardless of the size of the block they point to. So in
the following you can access the ith byte of a dynamic variable.
Var
BlockPtr : Pointer;
Index : Word;
begin
GetMem(BlockPtr,2000);
For Index := 1 to 1000 do
pWordArray(BlockPtr)^[Index] := Index;
end;
This allows you to allocate a block of 1000 words (2 bytes each in size)
numbered from 1 to 1000, each initialised with their index number.
STRING POINTERS
The PString type, the NewStr Function and the DisposeStr procedure are
usefull items, they are used throughout TurboVision and they allow you
to allocate and dispose of dynamic Strings easilly. You simply pass the
newstring function a String variable and it will return a Pointer to a
copy of that string stored in Dynamic Memory. You pass that pointer to
DisposeStr and it will return the Dynamic Memory used back to available
heap memory. Two nice tricks are that NewStr('') returns a nil pointer
wasting no Space, and dispose doesn't abort with an error if you dispose
a nil pString.
Sure you could implement this stuff yourself, and most of us have.
However it is all done in Objects, and it links in well with usefull
routines like the String Collection.
COLLECTIONS
Often as programmers we want to be able to store a collection of items,
we can use an array, the limitation being that the size must be
determined at compile time, and can't change, and can't be greater than
64k. The other option is to create a linked list structure, this is
middling difficult to create, but almost impossible to debug if you make
mistakes, and accessing an item requires traversal through preceding
items, hardly optimised.
Turbo vision introduces an Object called (AHA!) a Collection. It is in
fact a object encapsulate mutated resizing index array that allows
indexed access like an array, with the resizability of a linked list, and
because it indexes pointers to your objects you can store any dynamic
PNL #10 Page 22 March, 1992
variable or Object.
It's use is relatively simple, you simply create a descendant of
TCollection and overwrite it's freeItem method, to tell the collection
mechanisms how to get rid of an Item's memory, so that for example when
you dispose of the whole collection it knows how to dispose of the memory
for each item in the collection.
Initialise the Collection with two words, and Insert pointers to your
items already stored on the heap. The two variables are used internally
by the collection when building the index table, the first variable is
your best guess as to the starting number of items you will store and the
second is the amount you want the table to grow by each time you add more
variables than the current index can hold. I generally use 10 and 10, if
I don't know the nature of the collection. Have a look at StrList for a
simple example of how to store a collection of strings.
If you look at my main program loop it initialises a dynamic variable
pointing to a my new collection type, it then uses the newStr function to
store a string constant in the heap, and uses the collection method
insert(p : Pointer) to insert the pointer to that string into the
collection. It's a shorhand method that could be represented in longhand
as follows;
StrVar := 'Four';
StrPtr := NewStr(StrVar);
MyStringList^.Insert(StrPtr);
Then my main loop calls a new method I created for this object called
Print_Sentence, which then calls a method in Tcollection called
ForEach(Action : Pointer), this calls a far local (Important can not be
global) procedure pointer (PrintWord) for each Item in the collection.
Finally I dispose of my special collection using the TCollection.Done
destructor, which calls FreeAll, which in turn Deletes the index item and
calls freeItem for Each Item. Which is why we overwrote FreeItem, to
show our collection how to dispose of an item.
I Cheated a bit, there is actually a StringCollection Object provided in
Objects, that already knows all about pStrings, and sorts them into the
bargain, but I thought as an example it would be good to show a little of
the mechanism of creating a specialised collection type.
But there is much more to collections, the standard freeItem method is
not just abstract, it actually typecasts an object of type TObject over
the pointer to your Item, and calls IT's virtual destructor. Why, well
all turbo vision objects are in fact descended from a TObject, and if you
had inserted a TV object (Or any of your own objects descended ultimately
from a TObject), a vanilla TCollection would know all about how to
dispose of such an item, and we would not need to descend from
TCollection and override TCollection.FreeItem.
Unfortunately a pstring is not an object and thus can never be descended
from TObject, however a TCollection IS descended from TObject, and thus
you could have a Collection of Collections, or (and this is the good bit)
a collection of various objects, as long as each was descended from a
TObject.
I show an example of a Collection of Collections in the included file
PNL #10 Page 23 March, 1992
ReadIni.Pas, which is a simple unit to read a Windows type INI File. It
reads a Text file (See example Test.INI) and creates a collection of
TagCollections, each being a collection of parameters, ie:
TAGCollection = Object(tCollection)
|
+- PARAMCollection = Object(tCollection)
TAG : PString
|
+- PARAMItem = Object(TObject)
Param : pString
Vars : pString;
As the paramItem is a descendant of TObject, no special hadling is
required to add it to a PARAMcollection, which has it's own TAG string,
then all PARAMCOllections (One for each GROUP TAG) are stored in a
TAGCollection.
There is sparse documentation in the Source of READINI, To fully explain
the code here would bulk up this article, so I'll leave it as a
demonstration of what you can do with collections, and it could be a
usefull unit as it stands without you modifying anything.
STRINGLISTS
The String List is another usefull object in the OBJECTS unit. It is a
way of collecting a bunch of strings indexed by words. The way it works
is this, you create a program to make a StringList using a tStrListMaker
Object, which once Constructed with an INIT, simply requires you to use
tStrListMaker.Put(Index,TextString) which adds TextString to the Object
indexed by INDEX. You then save it to a file (More about this in Streams
and Resources). Now you create another program that reads a tStringList
from this File, then you simply ask the object for the text string that
corresponds to a particular index number.
The procedure is simple, I have included the sample files Make_Err,
Err_Desc, and Test to describe the procedure. You'll want to read on
about streams and resources as they are used in these files. However
I'll describe a few uses for String Lists. In the example I have given
Make_Err simply puts a Text String detailing each Runtime error, and
stores the description indexed with the run time error code. Desc_Err
simply finds out the exitcode and if non-Zero it prints out a
description. So what you say, well let's say for example that you have
to create a version of your program for a swedish audience. You simply
create a new ERRORS.STM file to put with your program, and you don't need
to change and recompile your code.
The REAL advantage is that now that the Europeans are finally getting
their act together with a common economic bloc, there will be a lot of
demand for programs that are multi lingual. If you use a stringList for
ALL text strings in your code, you'll be able to have users select upon
startup the language they wish to converse with the program in, and
simply activate THAT string List.
As a favour to me could those of you who are fluent in Non-English
languages, send in to the Newsletter a modified Make_Err for your
language, we'll compile them all together and distribute a multi lingual
Runtime error descriptor.
PNL #10 Page 24 March, 1992
STREAMS AND RESOURCES
About the most sophisticated file handling in Turbo pascal up until
TP5.0, was the TEXT file type. With the TP5.0 demo files Borland
introduced a sophisticated Object called a Stream that allows you to
easilly perform polymorphic Object File Input/Output. Turbo power
extended this with their own stream objects in Object Professional, and
finally we have it integrated in Turbo Vision. There is a great
discussion on Streams in Chapter 8 of the TV Guide, I shan't try to
repeat this here, however I'll give a general run down on the use of
Streams, and their application in the real world. I do plan to do a more
detailed tretise on Streams in the future (given time) based on a unit I
use called NetStrm, which extends the buffered stream to work on
Networks.
How does it work, well most turbo vision objects (And any Objects you
want to use with Streams) have two extra methods, a constructor usually
called Load, and a procedure called Store, which each take as a parameter
a stream variable, and do pretty much what they say they do. The
trickiness is that when you prepare to use objects in streams, you
register the Objects you will use.
Registering an Object entails sending information to the Objects unit
information so that it knows where to find your objects Load and Store
methods, a unique word called an ObjType, and the VMT link (Which within
OOPS Pascal uniquely defines an Object Type) which allows the Objects
unit to know the size of the dynamic object it must create during a load
construction.
When an object is stored to a file, it's ObjType number is first saved to
the Stream, then the Store method is called to put it's information onto
the file. Ok So what is the VMTLink for, well when you tell a stream to
load an object, it reads the OBJType number, then searches it's list of
registered objects and uses the VMTlink that it finds to do all the OOP
trickiness to construct the Object, then uses the pointer to the Load
constructor registered with the object, to fill the object with
information from the file.
So What? Well what this means is that your stream is asked to get an
object, it will return to you a pointer to an object, after constructing
it for you, WITHOUT KNOWING WHAT THE OBJECT IS. Ha, So what again. Well
it means that you can store any registered descendant of TObject with a
store and complimentary Load mechanism, and load it back again without
having to know at compile time the information you are sending to the
screen.
Resources are simply streams with an index comprising of key strings, in
the case of Make-Err and Desc_err I store the stringlist to a resource
under the keystring "ERRORDESC".
As an example of the use of Streams, Say you have 5 or 6 different record
types that have to be input and output to a file. In the 'Olden days'
you would use the case command to create a variant record, and each
record you stored would be the size of the LARGEST record case. This way
you simply declare a different Object for each record type, and you use
only as much space as each record needs. Furthermore if you have put a
method called EditRec into an abstract Object, and descended all your
PNL #10 Page 25 March, 1992
record objects from it, and in each case overriden Editrec to do the
individual record editing and manipulation you could simply do the
following;
Incognito := TStream.Get;
Incognito^.EditRec;
It's a lot easier if your object is a Turbo Vision View with record
information, and editing dialogs, but then you'd have to use the Turbo
Vision User Interface. Ah well, we'll all be using it sooner or later.
Refference sources
Turbo Vision Guide
Borland Intl
"Structured Programming"
Dr. DOBBS journal
November 1991
About the author
Richard Morris is the CEO of KHIRON Software, a Queensland,
Australia based Computer consultancy, that has been providing
contract programmers, and computer support to small to medium
business' sin 1985.
Richard can be contacted via the following
Fidonet: 3:640/372.5
IntlNet: 58:1100/378
Voice: (07) 812-3218
Post: C/- KHIRON Software
P.O. Box 544,
INDOOROOPILLY Qld 4068.
PNL #10 Page 26 March, 1992
=================================================================
FREQUENTLY ASKED QUESTIONS IN THE PASCAL ECHO
=================================================================
Q1. How do I pass an error level code when my program finishes?
A1. The halt procedure takes an optional parameter of type word. Thus -
halt(1);
terminates the program with an errorlevel of 1. If halt is used
without a parameter it is the same as -
halt(0);
Note: When a program is terminated using the halt procedure any
exit procedure that has previously been set up is executed.
Q2. How do I empty the keyboard buffer?
A2. There are several ways that this can be achieved. However the
safest is -
while Keypressed do ch := ReadKey;
This requires that a variable ch of type char is declared and the
crt unit be used. To do it without using a variable -
while Keypressed do while ReadKey = #0 do;
or if using TP6 with extended syntax enabled -
while KeyPressed do ReadKey;
If you do not wish to incur the substantial overhead involved with
the use of the CRT unit and there is no requirement for the program
to run under a multi-tasker -
var
head : byte absolute $40:$1c;
tail : byte absolute $40:$1e;
tail := head;
Q3. When I redirect the screen output of my programs to a file the file
is empty and the output still appears on the screen. What am I
doing wrong?
A3. You are probably using the CRT unit and its default method of
writing to stdout is by direct screen writes. In order to enable
output to be redirected all writes must be done by DOS. Setting the
variable DirectVideo to false has no effect on redirection as all it
does is use the BIOS for screen writes - not DOS.
PNL #10 Page 27 March, 1992
To enable redirection you must not use the CRT unit
OR
assign(output,'');
rewrite(output);
This will make all output go through DOS and thus can be redirected
if desired. To restore the default situation -
AssignCRT(output); rewrite(output);
Q4. How do I make a string that is lower or mixed case all uppercase?
A4. There are several ways to convert lower case characters to upper
case. Here are some of them.
As a procedure (excluding asm code this is the fastest way)
procedure StrUpper(var st: string);
var x : byte;
begin
for x := 1 to length(st) do
st[x] := UpCase(st[x]);
end;
As a function (slower but sometimes more convenient) -
function StrUpper(st: string): string;
var x : byte;
begin
StrUpper[0] := st[0];
for x := 1 to length(st) do
StrUpper[x] := UpCase(st[x]);
end;
Both the above are suitable for the English language . However
from version 4.0 onwards, DOS has had the facility to do this in a
way that is country (language) specific. I am indebted to Norbert
Igl for the basic routine. I have modified his code slightly. For
the anti-goto purists this is a good example of a goto that is
convenient, efficient, self-documenting and structured. The dos
calls would make this method the slowest of all.
function StrUpper(s: string): string;
{ Country specific string-to-uppercase conversion. }
{ Requires DOS unit }
label
fail;
var
regs : registers;
x : byte;
begin
PNL #10 Page 28 March, 1992
if lo(DosVersion) >= 4 then begin
with regs do begin
ax := $6521;
ds := seg(s);
dx := ofs(s[1]);
cx := length(s);
msdos(regs);
if odd(flags) then { the attempted conversion failed so }
goto fail;
end; { with }
end { if DOS >= 4.0 } else
fail:
for x := 1 to length(s) do
s[x] := UpCase(s[x]);
StrUpper := s;
end; { StrUpper }
Q5. When I include ANSI codes in a string and write that string to
the screen the actual codes appear on the screen, rather than the
results they are supposed to achieve.
A5. In order for ANSI codes to be interpreted, screen writes must be
directed through DOS and there must have been a suitable driver
loaded via the config.sys file at boot time. All output can be
directed through DOS and the driver by -
Not using the crt unit
OR -
assign(output,'');
rewrite(output);
in which case ALL screen writes are "ANSI code sensitive"
OR -
You can set up write procedures that will be "ANSI code sensitive".
(You will need an initialisation procedure to set this up.)
var
ansi : text;
procedure AssignANSI(var ansifile : text);
begin
assign(ansifile,'CON');
rewrite(ansifile);
end; { AssignANSI }
procedure WriteANSI(var st: string);
begin
write(ansi,st)
end; { WriteANSI }
PNL #10 Page 29 March, 1992
procedure WriteLnANSI(var st: string);
begin
writeANSI(st);
writeln(ansi);
end; { WriteANSI }
ObviousLy, if the ANSI.SYS driver (or an equivalent) is not
installed none of the above can work.
Setting the variable DirectVideo in the CRT unit to false will not
achieve the desired result as this merely turns off direct screen
writes and uses the BIOS for all screen output.
Q6. When I try to shell to DOS nothing happens. What am I doing wrong?
A6. In order to be able to execute any child process there must be
sufficient memory available for it to load and execute. Unless
you advise differently at compile time, a Turbo Pascal program
grabs all available memory for itself when it is loaded. To
reserve memory for a child process use the compiler memory
directive -
{$M 16384,0,0)
the default is -
{$M 16384,0,655360}
The first figure - StackMin - is the amount of memory to be
allocated for the stack:
Minimum is: 1024
Default is: 16384
Maximum is: 65520
The next figure - HeapMin -is the minumum amount of memory to be
allocated for the heap. If there is less memory available than
this figure the program will not load.
Minimum is: 0
Default is: 0
Maximum is: 655360 In practice it will be the amount of free
memory less the space required for the stack,
less the code space of the program. You
should set this to 0 unless your program uses
the heap. In that case, set it to the lowest
possible figure to prevent heap allocation
errors. In most cases it is best to leave it
at zero and do error checking within the
program for sufficient memory at allocation
time.
The last figure is the crucial on as regards child processes. It
should always be low enough to leave memory left over for a child
process and high enough not to cause problems for the program when
allocating heap memory.
PNL #10 Page 30 March, 1992
Minimum is: HeapMin
Default is: 655360
Maximum is: 655360 If less than the requested amount is
available no error is reorted. Instead all
available memory is allocated for heap use.
Q7. How do I shell to DOS?
A7. SwapVectors;
exec(GetEnv('COMSPEC','');
SwapVectors;
Read previous section on memory allocation.
I find that it is a good idea to write my own Exec function which
will do everything that is needed for me. I have it return an
integer value that is the DosError code.
function Exec(p1,p2: string);
begin
SwapVectors;
Dos.Exec(p1,p2);
SwapVectors;
Exec := DosError;
end;
This enables me to have a statement such as -
ReportError(Exec(GetEnv('COMPSEC'),''));
Now you can have an empty ReportError procedure or you can make it
report the error - whatever is suitable for you application.
Q8. When I execute a child process redirection does not work. Why?
A8. Redirection of a child process's output only works if it is run
under another copy of the command processor. So -
exec('YourProg.exe',' > nul'); will not work but
exec(GetEnv('COMSPEC'),'/c YourProg > nul'); will work.
Q9. How do I read an errorlevel from a child process?
A9. After executing a child process the errorlevel returned can be
read by calling the DosExitCode function which returns a word.
The low byte is the errorlevel. A full description is in the
manual.
If the command interpreter is the child process and it in turn
executes a child process then the errorlevel of the second child
process cannot be read without resorting to some trickery.
PNL #10 Page 31 March, 1992
Q10. When I read a text file that has lines exceeding 255 characters
I lose all those characters from the 256th one on each time there
is a line that exceeds that length. How can I prevent this?
A10. Turbo Pascal's readln procedure reads a line up to the 255th
character then skips to the next line. To get around this you
should declare a buffer at least as large as the longest possible
line and then use the read procedure. The best size for the
buffer is a multiple of 2048 bytes.
const
BufferSize = 2048;
LineLength = 78;
type
textbuffer = array[1..BufferSize] of char;
var
st : string;
f : text;
buffer : textbuffer;
function ReadTxtLn(var tf: text; var s: string; max: byte): integer;
{ Reads a string of a maximum length from a text file }
var
len : byte absolute s;
begin
len := 0;
{$I-}
while (len < max) and not eoln(tf) do begin
inc(len);
read(tf);
end;
if eoln(tf) then
readln(tf);
ReadTxtLn := IOResult;
{$I+}
end; { ReadTxtLn }
begin
assign(f,filename);
reset(f);
SetTextBuf(f,buffer);
while not eof(f) and (ReadTxtLn(f,st,LineLength) = 0) do
writeln(st);
close(f);
end.
Q11. How do I convert nul terminated asciiz strings to Turbo Pascal
strings?
A11. Here is a function that will do that -
function Asc2Str(var s; max: byte): string;
{ Converts an ASCIIZ string to a Turbo Pascal string }
{ with a maximum length of max. }
var starray : array[1..255] of char absolute s;
len : integer;
PNL #10 Page 32 March, 1992
begin
len := pos(#0,starray)-1; { Get the length }
if (len > max) or (len < 0) then { length exceeds maximum }
len := max; { so set to maximum }
Asc2Str := starray;
Asc2Str[0] := chr(len); { Set length }
end; { Asc2Str }
Q12. How can I tell if a particular bit of a variable is set or not?
How can I set it? How can I turn it off? How can I make a large
bit map and then determine if a particular bit - say bit 10000 is
on/of?
A12. This question, or a variation of it, is one of the most commonly
asked questions in the echo and there are several ways of doing what
is wanted. None are necessarily right or wrong. The way I will
describe is designed to take up as little code/data space as
possible. I do not attempt to explain the theory behind these
functions as this can be obtained from any good book.
The use of sets can be the best bit manipulation method if you have
control over the data being used. Here is an example of a byte
variable for a BBS program which sets various user access level flags.
Bit 0 = Registered User
1 = Twit
2 = Normal
3 = Extra
4 = Privileged
5 = Visiting Sysop
6 = Assistant Sysop
7 = Sysop
type
status_type = (Registered,
Twit,
Normal,
Extra,
Privileged,
VisitingSysop,
AssistantSysop,
Sysop);
status_level = set of status_type;
var
access_flags : status_level;
Let us assume you have someone who logs on and you wish to determine
his user access level. After reading access_flags from the user data
file -
if Sysop in access_flags then ....
To set the sysop flag -
access_flags := access_flags + [Sysop];
PNL #10 Page 33 March, 1992
To reset the sysop flag -
access_flags := access_flags - [Sysop];
However on many occasions using a set may not be a suitable method.
You may simply need to know if bit 5 is set or not. Here is the
method that I consider the best -
function BitIsSet(var V, bit: byte): boolean;
begin
BitIsSet := odd(V shr bit);
end;
To set a bit -
procedure SetBit(var V: byte; bit: byte);
begin
V := V or (1 shl bit);
end;
To reset a bit -
procedure ResetBit(var V: byte; bit: byte);
begin
V := V and not(1 shl bit);
end;
To toggle (flip) a bit -
procedure ToggleBit(var V: byte; bit: byte);
begin
V := V xor (1 shl bit);
end;
Now a bit map can be made up from an array of bytes. If stored on
the heap you can test any bit up to number 524159 (zero based).
Here's how.
type
map = array[0..maxsize] of byte;
{ set maxsize to number of bits div 8 -1 needed in the bit map }
function BitSetInBitMap(var x; numb : longint): boolean;
{ Tests the numb bit in the bitmap array }
var m: map absolute x;
begin
BitSetInBitMap := odd(m[numb shr 3] shr (numb and 7));
end;
procedure SetBitInBitMap(var x; numb: word);
{ Sets the numb bit in the bitmap array }
var m: map absolute x;
begin
m[numb shr 3] := m[numb shr 3] or (1 shl (numb and 7))
end;
procedure ResetBitInBitMap(var x; numb : longint);
{ Resets the numb bit in the bitmap array }
PNL #10 Page 34 March, 1992
var m: map absolute x;
begin
m[numb shr 3] := m[numb shr 3] and not(1 shl (numb and 7));
end;
procedure ToggleBitInBitMap(var x; numb : longint);
{ Toggles (flips) the numb bit in the bitmap array }
var m: map absolute x;
begin
m[numb shr 3] := m[numb shr 3] xor (1 shl (numb and 7));
end;
Q13. How can I find a particular string in any file - text or binary?
A13. The Boyer-Moore string search algorithm is considered to be the
fastest method available. However in a rare worst-case scenario it
can be slightly slower than a linear brute-force method. The
following demonstration program will show how it works and could
easily be modified to allow for command line paramters etc.
program BMSearchDemo;
type
bigarray = array[0..32767] of byte;
baptr = ^bigarray;
BMTable = array[0..255] of byte;
const
KeyStr : string = 'Put whatever you want found here';
fname : string = 'f:\Filename.txt';
var
Btable : BMtable;
buffer : baptr;
f : file;
result,
position : word;
offset : longint;
finished,
Strfound : boolean;
procedure MakeBMTable(var t : BMtable; var s);
{ Makes a Boyer-Moore search table. s = the search string}
{ t = the table }
var
st : BMtable absolute s;
slen: byte absolute s;
x : byte;
begin
FillChar(t,sizeof(t),slen);
for x := slen downto 1 do
if (t[st[x]] = slen) then
t[st[x]] := slen - x
end;
function BMSearch(var buff,st; size : word): word;
PNL #10 Page 35 March, 1992
{ Not quite a standard Boyer-Moore algorithm search routine }
{ To use: pass buff as a dereferenced pointer to the buffer}
{ st is the string being searched for }
{ size is the size of the buffer }
{ If st is not found, returns $ffff }
var
buffer : bigarray absolute buff;
s : array[0..255] of byte absolute st;
len : byte absolute st;
s1 : string absolute st;
s2 : string;
count,
x : word;
found : boolean;
begin
s2[0] := chr(len);
{ sets the length to that of the search string }
found := false;
count := pred(len);
while (not found) and (count < (size - len)) do begin
if (buffer[count] = s[len]) then
{ there is a partial match } begin
if buffer[count-pred(len)] = s[1] then
{ less partial! } begin
move(buffer[count-pred(len)],s2[1],len);
found := s1 = s2;
{ if = it is a complete match }
BMSearch := count - pred(len);
{ will stick unless not found }
end;
inc(count); { bump by one char - match is irrelevant }
end
else
inc(count,Btable[buffer[count]]);
{ no match so increment maximum }
end;
if not found then
BMSearch := $ffff;
end; { BMSearch }
begin
new(buffer);
assign(f,fname);
reset(f,1);
offset := 0;
MakeBMTable(Btable,KeyStr);
repeat
BlockRead(f,buffer^,sizeof(buffer^),result);
position := BMSearch(buffer^,KeyStr,result);
finished := (result < sizeof(buffer^)) or (position <> $ffff);
if position = $ffff then
inc(offset,result);
Strfound := position <> $ffff;
until finished;
close(f);
if Strfound then
writeln('Found at offset ',offset)
PNL #10 Page 36 March, 1992
else
writeln('Not found');
end.
Q14. How can I put a apostrophe in a string?
A14. Just put in extra apostrophes. If you want st to be equal to
the string - The word 'quoted' is in quotes do this -
st := 'The word ''quoted'' is in quotes';
if you want the following to be written to screen -
'This is a quoted string'
do this -
writeln('''This is a quoted string''');
Q15. What are the best books to purchase to help me learn Turbo Pascal?
A15. There are many good books for learners. Here are a few -
Complete Turbo Pascal - Third Edition - Jeff Duntemann
Mastering Turbo Pascal 6 - Tom Swann
Turbo Pascal - The Complete Reference - O'Brien.
For advanced users there are also many good books. Here are a
few
that I have found useful - (Those marked with an asterisk are
not
purely for Turbo Pascal)
Turbo Pascal 6 - Techniques and Utilities - Rubenking
Turbo Pascal Internals - Tischer
* PC System Programming for Developers - Tischer
* Undocumented DOS - Schulman
Any learner would be well advised to obtain a well known library
such
as Technojock's Turbo Toolkit (TTT) which is shareware and study
the
source code.
Trevor Carlsen
(TeeCee)
PNL #10 Page 37 March, 1992
=================================================================
Running 80286 Turbo Pascal Programmes on 8088/8086 Computers
=================================================================
[ Editor's note: Intentionally, the author uses English spelling instead
of American spelling ]
Introduction
When Borland released Turbo Pascal Version 6.0 in 1990, they added many
impressive features to the compiler which reaffirmed Turbo Pascal as the
de facto standard Pascal compiler for PCs. One such feature is the
ability to generate iAPX286 instructions for added speed on AT or higher
machines; unfortunately, they forgot (neglected?) to add a check to the
RTL (runtime library) for whether or not an iAPX286 was actually present.
Any programme compiled in the {$G+} state will undoubtedly "hang" when
executed on an 8088 or 8086-based computer.
In what seems an afterthought, Borland added a small demonstration
programme called "TEST286" in the \DEMOS subdirectory of your Turbo
Pascal subtree, and wrote a small mention in the "HELPME!.DOC" file.
Following Borland's own advice, however:
"If you want to put code like this in a program with
{$G+} enabled, put the test and halt code in the
initialization section of the first unit in the main
program's USES clause."
What follows is a description of a unit (provided) which does exactly
that. Place the unit's label immediately after the "uses" keyword before
any other unit labels, and forget about it; at runtime when the
initialisation code is executed and no 80286, 80386 or 80486 is detected,
a message is displayed informing the user of the reason why the programme
immediately thereafter exits (returning an errorlevel of one).
Directives Overview
This section merely explains the logic behind each of the compiler
directives used for the benefit of novices, and can be ignored by more
competant programmers.
As presented, the unit is compiled in the {$G-} state to ensure proper
execution on computers based on the 8088 or 8086 processor. Further, it
is compiled with far calls and overlay ability enabled (viz., {$F+} and
{$O+}) so upon completion in large applications, the code can be swapped
out (although this shouldn't be too necessary as the entire unit is less
than one kilobyte).
The routine exhibited no problems on the test machines (an 80486, an
80386 AST and an NEC V20-based XT), so has all debugging facilities
disabled ({$D-,I-,L-,R-,S-}). This should not prove a problem in any
programmes in which you have debugging enabled, unless you wish to trace
through the code.
In addition to not including debugging information, the "usual" code
optimisation switches are enabled ({$B-,V-}). Since word-aligned data
has no effect on the 8088, yet increases execution speed on all 80x86
PNL #10 Page 38 March, 1992
processors, it is enabled ({$A+}). There is no use of the "extended
syntax" option (again, a Version 6.0 enhancement), so it is disabled with
{$X-}.
The addition of {$E-} and {$N-} is merely to "complete" the switch
directives, and indicate that there is no need for any additional numeric
or emulated floating point processing.
Unit Description
As there are no functions or procedures to call (variables to read and
alter, constants to use, et al.), the "interface" section of the unit
remains void (empty).
Both the errorlevel returned, and the message displayed are defined as
constants so that they can be easily altered by an external setup or
configuration utility. The errorlevel can be any value from zero (0) to
two hundred and fifty-five (255), and the error message can be upto
eighty (80) characters in length. For users of Object Professional's
OpClone unit, the string constant "i286 config data 1.00" is provided.
The one internal variable declared, "Is286Able", is used to pass the
result of the detection routine to the remainder of the code; this was
necessary as no separate function is used (to keep execution speed to the
bare minimum), and standard functions and procedures (such as WriteLn()
and Halt()) are not callable from within the built-in inline assembler
(one short-coming of BASM).
The core of the original function provided in TEST286 is:
asm
pushf { Push flags register onto the stack }
pop bx { Pop a word from the stack, store in BX }
and bx, 00FFFh { Logical AND operands, result in BX }
push bx { Push word contents of BX on the stack }
popf { Pop from stack into flags register }
pushf { Push flags register onto the stack }
pop bx { Pop a word from the stack, store in BX }
and bx, 0F000h { Logical AND operands, result in BX }
cmp bx, 0F000h { Compare operands, update status flags }
mov ax, 00h { Store word 0x0000 (zero) in AX }
jz @@1 { If the zero flag is set, jump to "@@1" }
mov ax, 01h { Store word 0x0001 (one) in AX }
@@1: { Internal label "@@1", end of routine }
end;
The essential logic here is that for processors earlier than the 80286
(viz., 8088, 8086, 80188, 80186, V20 and V30) bits 12 to 15 of the CPU
flags register cannot be cleared. This routine merely tries to clear
those bits (without disturbing the others), and if it is unable to do so,
assumes an iAPX286 or higher (i386, i486) is not present.
For the supplied unit, the core is:
asm
xor ah, ah { Logical XOR operands, result in AH }
pushf { Push flags register onto the stack }
PNL #10 Page 39 March, 1992
pop bx { Pop a word from the stack, store in BX }
and bx, 00FFFh { Logical AND operands, result in BX }
push bx { Push word contents of BX on the stack }
popf { Pop from stack into flags register }
pushf { Push flags register onto the stack }
pop bx { Pop a word from the stack, store in BX }
and bx, 0F000h { Logical AND operands, result in BX }
cmp bx, 0F000h { Compare operands, update status flags }
je @@1 { If the zero flag is set, jump to "@@1" }
inc ah { Increment the contents of AH by one }
@@1: { Internal label "@@1", store result of }
mov [Is286Able], ah { detection in "Is286Able" variable }
end;
Logically, the two code segments are identical (ignoring the last "mov
[Is286Able], ah" instruction above). If we compare their logic structure
in English:
(Original Code)
1. Clear flag bits 12-15.
2. Compare - did they clear?
3. Set the result to zero (false).
4. If the comparison in #3 resulted in the zero flag
being set, ie., the bits did NOT clear, jump to
the end of the routine.
5. The bits DID clear, so set the result to one (true).
(New Code)
1. Set the result to zero (false).
2. Clear flag bits 12-15.
3. Compare - did they clear?
4. If the comparison in #3 resulted in the zero flag
being set, ie., the bits did NOT clear, jump to
the end of the routine.
5. The bits DID clear, so increment the result to one (true).
Up to this point, the comparison has been discussed as resulting in the
zero flag being set or cleared; to understand why, you must remember that
comparisons are conducted by subtraction, so that if two items equal each
other numerically, their difference is zero (hence the the zero flag is
set). To indicate more accurately the logic of the routine, the "jz"
(jump if zero set) instruction was replaced with the "je" (jump if equal
- ie., zero set) instruction. Both are functionally identical.
Saving Clock Cycles
The differences remain now with two instructions: "xor ah, ah" and "inc
ah". Since only eight bits are needed for the result, AH is used rather
than AX in its entirety. The zeroing of the register with XOR instead of
MOV ("xor ah, ah" instead of "mov ah, 0") saves one clock cycle on 8086
processors (none on 80286, 80386 or 80486 processors) and works since
performing a logical exclusive OR (as opposed to a logical inclusive OR)
on a number with itself always results in zero.
Moving the placement of the "set-result-to-false" instruction to the
start not only makes more sense, but is necessary as the XOR instruction
modifies the zero flag (which then could not be used in the ensuing jump
instruction).
PNL #10 Page 40 March, 1992
Rather than loading AH with one ("mov ah, 1" indicating "true"), the
increment instruction is used ("inc ah") as, with XOR, it saves an extra
valuable clock cycle on 8086 processors. Admittedly, if the host CPU is
an 8086 the increment instruction is never reached, but it does not hurt
to optimise at the instruction level.
Remaining Code
The last five lines of the routine proper are in Pascal (as explained
above, BASM doesn't allow calling standard procedures and functions).
These merely check the status of the "Is286Able" variable, and if the
assembly code set it to "false", print a message before exiting.
Test Programme
A driver programme to test the unit is simply:
program Testi286;
{$G+}
uses
i286;
{-If a '286 is absent, the init code of the unit will exit}
begin
WriteLn('Obviously an 80286 or higher is in this machine.')
end. { Testi286 }
Conclusion
While many will claim the saving of two (well, one) clock cycles is no
more than academic, this was not the main aim of the unit. The "i286"
unit provides a very easy to use, "plug 'n' play" method of detecting and
exiting which requires no further effort on the part of the programmer.
David J. N. Begley
58:2100/142@intlnet, 3:712/211.3@fidonet
Department of Computing, Faculty of Science and Technology
University of Western Sydney, Nepean
PNL #10 Page 41 March, 1992
=================================================================
Conclusion
=================================================================
That's it for now.
In the next issue, I will publish the first part of what I call the
Beginner's Toolbox. It is a set of units to help beginners to program
easily. It will include input/output routines, a help system, a menuing
system, etc...
I thank everybody who participated in the rebirth of PNL. Richard
Morris, the editor over-the-pond has collected most of the articles
here. Without him, there would have been only 2 articles here by March
1st, 1992.
Again, I beg you all to send in articles. Everybody has his/her own
techniques to program, comments to share with others. You can send in
book reviews or software reviews,
The newsletter depends on your contribution...
Anybody interested in publishing an article can request the Article
Specifications with the magic name ARTSPEC on FidoNet 1:167/405 - It
will also be available soon from Richard Morris, FidoNet 3:640/372.5.
If you would like to receive back issues of PNL directly from me, send a
diskette and $2.00 for shipping. Don't forget to include your address.
Send your order to:
Alex Boisvert
86 Bryant St.
Sherbrooke, Quebec
Canada J1J 3E4
If you are a SysOp that will regularly carry PNL and would like to have
your bulletin board listed as such, here, send me a message either by
postal mail or at one of the electronic addresses given on the title
page, with your bulletin board's name, phone number, and your name.
Distribution List
The following is the phone numbers to bulletin boards known to carry
PNL. If you would like your bulletin board's name and number added to
or deleted from this list, please send me a message at one of my many
addresses. I can not guarantee whether a listed board will have any
particular issue, however.
Thieve's World ......................... Phone: (713) 463-8053
Hippocampus ............................ Phone: (203) 484-4621
Turbo City BBS ......................... Phone: (209) 599-7435
The Final Frontier BBS.................. Phone: (518) 761-0869